#include <GameEngine/Gameplay/States/lock_state.h>

#include <GameEngine/Gameplay/Objects/object_inventory.h>
#include <GameEngine/Gameplay/Objects/object_container.h>
#include <GameEngine/Core/Memory/memory_manager.h>


#ifdef GAMEENGINE_NAMESPACES
namespace GameEngine {
#endif

//=================================================================================


//! Constructor
/*!
 @param state The current lock state of the object
 @note Keys are defaulted to "no key opens that object"
 */
LockState::LockState(State state) : state_(state), keys_(nil) {}

//! Copy constructor
/*!
 @param rhs The object to copy
 */
LockState::LockState(const LockState& rhs) : state_(rhs.state_), keys_(nil) {
	if ( rhs.keys_ == nil ) return;
	keys_ = MemoryManager::instance()->allocate< QList<ObjectInventory*> >();
	keys_->operator+=(*rhs.keys_);
}

//! Assignment operator
/*!
 @param rhs The object to copy
 */
LockState& LockState::operator=(const LockState& rhs) {
	if ( &rhs != this ) {
		state_ = rhs.state_;
		if ( keys_ ) {
			MemoryManager::instance()->desallocate(keys_);
			keys_ = nil;
		}
		if ( rhs.keys_ != nil ) {
			keys_ = MemoryManager::instance()->allocate< QList<ObjectInventory*> >();
			keys_->operator+=(*rhs.keys_);
		}
	}
	return *this;
}

//! Destructor
LockState::~LockState() {
	MemoryManager::instance()->desallocate(keys_);
}

//! Test if the given container contains a key which opens the object
/*!
 @return True if the given container contains a key which opens the object, false otherwise
 @note If the object is not locked, always returns true
 */
bool LockState::can_open(ObjectContainer* container) const {
	if ( state_ == Unlocked ) return true;
	if ( container == nil ) return false;

	// Iterate over the content of the container
	for ( ObjectContainer::InventoryList::const_iterator it = container->inventory().begin(); it != container->inventory().end(); ++it ) {
		if ( find_key(*it) == true ) return true;
	}

	// If no key was found, return false
	return false;
}

//! Test if the given list of objects contains a key which opens the locked object
/*!
 @return True if the given list of objects contains a key which opens the locked object, false otherwise
 @note If the object is not locked, always returns true
 */
bool LockState::can_open(const QList<ObjectInventory*>& objects) const {
	if ( state_ == Unlocked ) return true;

	// Iterate over the content of the container
	for ( QList<ObjectInventory*>::const_iterator it = objects.begin(); it != objects.end(); ++it ) {
		if ( find_key(*it) == true ) return true;
	}

	// If no key was found, return false
	return false;
}

//! Accessor to the lock state of the object (read only)
/*!
 @return The lock state of the object
 */
LockState::State LockState::state() const {
	return state_;
}

//! Accessor to the lock state of the object (read-write)
/*!
 @return A reference to the lock state of the object
 */
LockState::State& LockState::state() {
	return state_;
}

//! Add the given list of keys to the keys that can open the object
/*!
 @param keys The list of keys to add to the lock state
 @return True if at least one of the keys could be added, false otherwise. Keys may not be
         added if all of them already open the object.
 */
bool LockState::add_keys(const QList<ObjectInventory*>& keys) {
	if ( keys.empty() ) return false;

	// If no key list existed, create it
	if ( keys_ == nil ) {
		keys_ = MemoryManager::instance()->allocate< QList<ObjectInventory*> >();
		keys_->operator+=(keys);
		return ( keys_->empty() == false );
	}

	// Otherwise, add objects to the key list
	bool res = false;
	for ( QList<ObjectInventory*>::const_iterator it = keys.begin(); it != keys.end(); ++it ) {
		res |= add_key(*it);
	}
	return res;
}

//! Add the given key to the list of keys
/*!
 @param key The key to add
 @return True if key could be added, false otherwise. Key may not be added if it already
         opens the object.
 */
bool LockState::add_key(ObjectInventory* key) {
	// If no key list existed, create it
	if ( keys_ == nil ) {
		keys_ = MemoryManager::instance()->allocate< QList<ObjectInventory*> >();
		keys_->operator+=(key);
		return ( keys_->empty() == false );
	}

	// Otherwise, check if the key is not already in the list
	if ( find_key(key) == true ) return false;
	keys_->operator+=(key);
	return true;
}

//! Remove the given list of keys from the keys that can open the object
/*!
 @param keys The list of keys to remove from the lock state
 @return True if at least one of the keys could be removed, false otherwise. Keys may not be
         removed if none of them already opened the object.
 */
bool LockState::remove_keys(const QList<ObjectInventory*>& keys) {
	// If no key list existed, nothing to do
	if ( keys_ == nil ) return false;

	// Otherwise, add objects to the key list
	bool res = false;
	for ( QList<ObjectInventory*>::const_iterator it = keys.begin(); it != keys.end(); ++it ) {
		res |= remove_key(*it);
	}
	return res;
}

//! Remove the given key from the list of keys
/*!
 @param key The key to remove
 @return True if key could be removed, false otherwise. Key may not be removed if it did
         not already open the object.
 */
bool LockState::remove_key(ObjectInventory* key) {
	// If no key list existed, nothing to do
	if ( keys_ == nil ) return false;

	// Otherwise, remove the key from the list
	bool res = ( keys_->removeAll(key) > 0 );

	// Cleanup keys if necessary
	if ( keys_->empty() ) {
		MemoryManager::instance()->desallocate(keys_);
		keys_ = nil;
	}

	// Return result
	return res;
}

//! Try to find a key in the list of keys
/*!
 @param key The key to find
 @return True if key was found, false otherwise
 */
bool LockState::find_key(const ObjectInventory* key) const {
	if ( keys_ == nil ) return false;
	for ( QList<ObjectInventory*>::const_iterator it = keys_->begin(); it != keys_->end(); ++it ) {
		if ( (*it) == key ) return true;
	}
	return false;
}

//! Accessor to the list of keys that open this object
/*!
 @return The list of keys that open this object (nil if no key opens that object)
 */
QList<ObjectInventory*>* LockState::keys() const {
	return keys_;
}

#ifdef GAMEENGINE_NAMESPACES
} // namespace GameEngine
#endif
